There is no spoon

User:Wormbo/Creating Anti TCC mini-games

From Unreal Wiki, The Unreal Engine Documentation Site
Jump to: navigation, search

Remember that PlayWaitingGame command Anti TCC used to kick people for before I added the secured console? Well, this feature has been put to good use now and can launch server-configurable mini-games for spectators. The only problem is: There are no games for it yet. This article explains the interface such mini-games need to satisfy and potential strategies for implementing them.

Anti TCC mini-game interface

The interface is as simple as it could possibly get. Whenever Anti TCC catches the console command Mutate PlayWaitingGame (or just PlayWaitingGame if the console is secured), it spawns an instance of the class specified in WaitingGameClass=... under [AntiTCC2009r?.MutAntiTCCFinal]. This configuration option isn't exposed to the webadmin interface yet, but spectators will see a corresponding info message during initial checks in their console. The code in the Anti TCC mutator spawning the game class instance is roughly as follows:

function Mutate(string MutateString, PlayerController Sender)
{
  if (Sender != None && Sender.PlayerReplicationInfo.bOnlySpectator && MutateString ~= "PlayWaitingGame" && WaitingGameClass != None) {
    if (Level.TimeSeconds < Sender.NextLoginTime) {
      return;
    }
    Sender.NextLoginTime = Level.TimeSeconds + Sender.LoginDelay;
    Spawn(WaitingGameclass, Sender);
    return;
  }
  Super.Mutate(MutateString, Sender);
  // ...
}

Using NextLoginTime and LoginDelay lowers the potential for abuse by limiting the number of spawns within a given time interval. Anti TCC restricts the login delay to values greater than zero, with the default being 1.0 game seconds.

The WaitingGameClass itself must be a subclass of Info and reside in a package the client has access to. That only means the package must not have the ServersideOnly flag and should have the AllowDownload flag. It will automatically be added to the ServerPackages by Anti TCC. The game name displayed in the client info message about a mini-game being available defaults to the class name, but can be customized by implementing the GetLocalString() function in that class. Anti TCC will not pass any of the optional parameters to it.

Example implementation:

class ExampleGame extends Info;
 
var localized string GameName;
 
static function string GetLocalString(optional int MsgSwitch, optional PlayerReplicationInfo PRI1, optional PlayerReplicationInfo PRI2)
{
  return default.GameName;
}
 
defaultproperties
{
  GameName = "Mini-Game example"
}

Of course this example doesn't do anything yet, except stay around and take up memory. (This is actually a bad thing!)

Handling start requests

The interface is extremely simple and pushes all responsibilities to the mini-game implementation. As you can see in the previous section, Anti TCC unconditionally spawns the WaitingGameClass with the requesting PlayerController as Owner. An implementation needs to handle the case where a player sends PlayWaitingGame while the game is already open.

One potential solution for this problem could be keeping a globally unique instance of the WaitingGameClass and additional instances only notify that main instance. Here's a potential way to do this:

function PreBeginPlay()
{
  local ExampleGame Game;
  local PlayerController Requester;
 
  Requester = PlayerController(Owner);
 
  // try to find an active instance
  foreach DynamicActors(class'ExampleGame', Game) {
    if (Game != Self) {
      Game.StartGame(Requester); // start the game on the main instance
      Destroy(); // get rid of this additional instance
      return;
    }
  }
 
  // no active instance found, initialize this one as the main instance
  Initialize();
 
  // start the game for the requesting player
  if (Requester != None)
    StartGame(Requester);
}
 
/**
Initialize the main instance.
This could, for example, load stats or game states from previous games
if you implemented them in a way that stores them serversidely.
*/
function Initialize()
{
  // ...
}
 
/**
Actually start a game for the specified player.
This function should make sure the player isn't already playing.
*/
function StartGame(PlayerController Player)
{
  // ...
}

The implementation of StartGame() depends on how you handle the replication part. It's probably a good idea to have one replicated actor per player and maintain a list of them in the main WaitingGameClass instance.